<?php
class Arr extends FluentExt{
	function __construct(){
		$params = func_get_args();
		if(count($params) > 1) 	$this->c = range($params[0], $params[1]);
		else					$this->c = is_array($params[0]) ? $params[0] : array();
		$this->d 				= 1;
		$this->t				= array();
	}
	
	static function fluent($arr = array()){ return new Arr($arr); }
	
	static function fluent2d($arr){ return self::fluent($arr)->make2d(); }
	
	function apply2d(){
		$params 	= func_get_args();
		$f 			= array_shift($params);
		foreach($this->c as &$a){
			$param_str = implode(',', $params);
			eval("\$a = \$a->$f($param_str);");
		}
		return $this;	
	}
	
	function apply2sub(){
		$params 	= func_get_args();
		$params[0]	= "'{$params[0]}'";
		foreach($this->c as &$a){
			$param_str = implode(',', $a->get_place_holder($params));
			eval("\$a = \$a->apply($param_str);");
			$a->t = array();
		}
		return $this;
	}
	
	function apply2field(){
		$params = func_get_args();
		$f 		= array_shift($params);
		$field 	= array_shift($params);
		foreach($this->c as &$sub){
			$cur_params 	= $this->fill_ret($params, $sub->c[$field]);
			$sub->c[$field] = call_user_func_array($f, $cur_params);
		}
		return $this;
	}
	
	function map($f = null){
		if($this->d == 1){
			$this->c = array_map($f, $this->c);
			return $this;
		}else
			return $this->apply2d('map', "'$f'"); 
	}
	
	function make2d($arr = false){
		$arr 		= $arr == false ? $this->c : $arr;
		$this->c 	= array();
		foreach($arr as $key => $a) $this->c[$key] = new Arr($a);
		$this->d 	= 2;
		return $this;
	}
	
	function makeStrs(){
		foreach($this->c as $key => $a){
			if(is_string($a))
				$this->c[$key] = new Str($a);
		}
		return $this;
	}
	
	function normalStrs(){
		foreach($this->c as $key => $a){
			if($a instanceof Str)
				$this->c[$key] = $a->c;
		}
		return $this;
	}
	
	function normal2d($ret = false){
		foreach($this->c as $key => $a) $rarr[$key] = $a->c;
		return $ret == false ? $this->ret($rarr) : $rarr;
	}
	
	function serial(){
		return new Str($this->d == 1 ? serialize($this->c) : serialize($this->normal2d()->c));
	}
	
	function flatten($key = null){
		if($key == null){
			$rarr = array();
			foreach($this->c as $a)
				$rarr = array_merge($rarr, $a->c);
		}else
			foreach($this->c as $a) $rarr[] = $a->c[$key];
		$this->d = 1;
		return $this->ret($rarr);
	}
	
	function arr(){ return $this->c; }
	
	function strApply(){
		$params 	= func_get_args();
		$f 			= array_shift($params);
		$param_str 	= implode(',',$params);
		foreach($this->c as &$a){
			if($a instanceof Str)
				eval("\$a = \$a->$f($param_str);");
		}
		return $this;
	}
	
	function setIf($expr, $assign){
		foreach($this->c as &$a){
			$str_q 	= $a instanceof Str;
			$tmp 	= $str_q ? $a->c : $a;
			eval($this->fix_expr($expr, $tmp));
			if($result){
				if($str_q) 	$a->c 	= $assign;
				else		$a 		= $assign;
			}
		}
		return $this;
	}
	
	function out(){ print_r($this->c); }

	function isEmpty(){ return empty($this->c); }
	
	function equal($arr){ return $this->c == $arr ? true : false; }
	
	function reverse(){ $this->c = array_reverse($this->c, true); return $this; }
	
	function merge($arr){ $this->c = array_merge($this->c, $arr); return $this; }
	
	function fill($start, $length, $value){
		$this->c = array_fill($start, $length, $value);
		return $this;
	}
	
	function key($val, $strict = false){ return array_search($val, $this->c, $strict); }
	
	function first(){ reset($this->c); return current($this->c); }
	
	function slice($offs, $length = NULL, $preserve = true){
		$this->c = array_slice($this->c, $offs, $length, $preserve);
		return $this;
	}
	
	function tail(){ return $this->slice(1); }
	
	function uniq(){ $this->c = array_unique($this->c); return $this; }
	
	function unshift(){
		$params = func_get_args();
		return $this->ret(array_merge($params, $this->c));
	}
	
	function pop(){ return array_pop($this->c); }
	
	function shift(){ return array_shift($this->c); }
	
	function push(){
		$args 		= func_get_args();
		$this->c 	= array_merge($this->c, $args);
		return $this;
	}
	
	function values(){ $this->c = array_values($this->c); return $this; }
	
	function keys($search = '', $strict = false){
		if(empty($search))
			$this->c = array_keys($this->c);
		else
			$this->c = array_keys($this->c, $search, $strict);
		return $this;
	}
	
	function last(){ $tmp = array_slice($this->c, -1, 1, false); return $tmp[0]; }
	
	function includes($el, $strict = null){ return $this->key($el, $strict) === false ? false : true; }
	
	private function ret($arr){ $this->c = $arr; return $this; }
	
	function join($sep = ' '){ return new Str(implode($sep, $this->c)); }
	
	function length(){ return count($this->c); }
	
	function isect($arr){ $this->c = array_intersect($this->c, $arr); return $this; }
	
	function isectKey($arr){ 
		$this->c = array_intersect_key($this->c, $arr); return $this; 
	}
	
	function diff($arr){ $this->c = array_diff($this->c, $arr); return $this; }
	
	function at($key){ 
		return $this->d == 1 ? $this->c[$key] : $this->apply2d('at', "'$key'"); 
	}
	
	function sort($flags = null, $key = null){
		if($this->d == 1){
			switch($flags){
				case'num': 		$flag = SORT_NUMERIC; break;
				case'string': 	$flag = SORT_STRING; break;
				default: 		$flag = SORT_REGULAR; break;
			}
			asort($this->c, $flag); 
			return $this;
		}else{
			foreach($this->c as $sub) $s_col[] = $sub->c[$key];
			$s_type = is_string($s_col[0]) ? SORT_STRING : SORT_NUMERIC;
			$arr 	= $this->normal2d()->c;
			$flag 	= $flags == 'desc' ? SORT_DESC : SORT_ASC;
			if($s_type == SORT_STRING) $s_col = array_map('strtolower', $s_col);
			array_multisort($s_col, $s_type, $flag, $arr);
			return self::fluent2d($arr);
		}
	}
	
	function numeric(){
		foreach($this->c as $sub) $res[] = $sub;
		return $this->ret($res);
	}
	
	function plot($labels = false){
		foreach($this->c as $key => $sub){
			foreach($sub->c as $subKey => $value)
				$res[$subKey][] = array($key, $value);
		}
		
		if(!$labels)
			return $this->ret($res)->numeric();
		else{
			foreach($res as $sub)
				$ret[] = array("label" => array_shift($labels), "data" => $sub);
			return $this->ret($ret);
		}
	}
	
	function sum($keys, $ctrlKey = false){
		if($this->d == 1){
			return array_sum($this->c);
		}else{
			$keys = explode(',', $keys);
			if(!empty($ctrlKey)){
				foreach($this->c as $sub){
					foreach($keys as $key){
						if(isset($res[ $sub->c[$ctrlKey] ][$key]))
							$res[ $sub->c[$ctrlKey] ][$key] += $sub->c[$key];
						else
							$res[ $sub->c[$ctrlKey] ][$key] = $sub->c[$key];
					}
				}
				return $this->ret($res)->make2d();
			}else{
				foreach($this->c as $sub){
					foreach($keys as $key){
						if(isset($res[$key])) 	$res[$key] += $sub->c[$key];
						else					$res[$key] = $sub->c[$key];
					}
				}
				return $this->ret($res);
			}
		}
	}
	
	function trim(){ return $this->compact(); }
	
    function json($arr = false) {
    	$arr 			= !$arr ? $this->c : $arr;
        $parts	 		= array();
        $is_list 		= false;
        $keys 			= array_keys($arr);
        $max_length 	= count($arr)-1;
        if(($keys[0] == 0) and ($keys[$max_length] == $max_length)) {
            $is_list = true;
            for($i=0; $i<count($keys); $i++) {
                if($i !== $keys[$i]) {
                    $is_list = false;
                    break;
                }
            }
        }
        foreach($arr as $key=>$value) {
            if(is_array($value)) {
                if($is_list) $parts[] = self::json($value);
                else $parts[] = '"' . $key . '":' . self::json($value);
            } else {
                $str = '';
                if(!$is_list) 				$str = '"' . $key . '":';
                if(is_numeric($value)) 		$str .= $value; //Numbers
                elseif($value === false) 	$str .= 'false'; //The booleans
                elseif($value === true) 	$str .= 'true';
                else 						$str .= '"' . addslashes($value) . '"';
                $parts[] = $str;
            }
        }
        $json = implode(',',$parts);
        if($is_list) return '[' . $json . ']';
        return '{' . $json . '}';
    }
     
	private function fix_expr($expr, $replace){ 
		return "\$result = ".str_replace('??', $replace, $expr).";"; 
	}
	
	private function common_eval($expr, $bool2, $doKey = false){
		foreach($this->c as $key => $a){
			$tmp 		= $doKey == false ? $a : $key;
			$replace 	= ($a instanceof Str && !$doKey) ? $a->c : $tmp;
			eval($this->fix_expr($expr, $replace));
			if($result == $bool2) $rarr[$key] = $a;
		}
		return $this->ret($rarr);
	}
	
	function replace($arr){ $this->c = $arr; return $this; }
	
	private function common_2d($f, $expr, $doKey, $key){
		$old 		= $this->normal2d(true);
		$doKey 		= $doKey == false ? 'false' : 'true';
		$new 		= $this->apply2d($f, "\"$expr\"", $doKey)->compact($key)->normal2d(true);
		$this->c 	= $old;
		$this->make2d();
		return $this->isectKey($new);
	}
	
	function collect($expr, $doKey = false, $key = false){
		if($this->d == 1) 	return $this->common_eval($expr, true, $doKey);
		else				return $this->common_2d('collect', $expr, $doKey, $key);
	}
	
	function del($expr, $doKey = false, $key = false){ 
		if($this->d == 1) 	return $this->common_eval($expr, false, $doKey);
		else 				return $this->common_2d('del', $expr, $doKey, $key);
	}
	
	function delAt($key){
		foreach($this->c as $key1 => $a)
			if($key1 != $key) $rarr[$key1] = $a;
		return $this->ret($rarr);
	}
	
	function compact($onKey = false){
		foreach($this->c as $key => $a){
			$tmp = $this->d == 1 ? $a : $a->c;
			$tmp = $onKey === false ? $tmp : $tmp[$onKey];
			if(!empty($tmp)) 
				$rarr[$key] = $a;
		}
		return $this->ret($rarr);
	}
	
	function add($arr){ $this->c += $arr; return $this; }
	
	function assign($key){
		$val = $this->d == 1 ? $this->c : $this->normal2d()->c;
		$this->smarty->assign($key, $this->c); 
		return $this; 
	}

}
